#include "config.h"
#include "globals.h"

#include "errno.h"

#include "util/init.h"
#include "util/debug.h"
#include "util/list.h"
#include "util/string.h"

#include "proc/kthread.h"
#include "proc/proc.h"
#include "proc/sched.h"

#include "mm/slab.h"
#include "mm/page.h"

kthread_t *curthr; /* global */
static slab_allocator_t *kthread_allocator = NULL;

#ifdef __MTP__
/* Stuff for the reaper daemon, which cleans up dead detached threads */
static proc_t *reapd = NULL;
static kthread_t *reapd_thr = NULL;
static ktqueue_t reapd_waitq;
static list_t kthread_reapd_deadlist; /* Threads to be cleaned */

static void *kthread_reapd_run(int arg1, void *arg2);
#endif

void
kthread_init()
{
        kthread_allocator = slab_allocator_create("kthread", sizeof(kthread_t));
        KASSERT(NULL != kthread_allocator);
}

/**
 * Allocates a new kernel stack.
 *
 * @return a newly allocated stack, or NULL if there is not enough
 * memory available
 */
static char *
alloc_stack(void)
{
        /* extra page for "magic" data */
        char *kstack;
        int npages = 1 + (DEFAULT_STACK_SIZE >> PAGE_SHIFT);
        kstack = (char *)page_alloc_n(npages);

        return kstack;
}

/**
 * Frees a stack allocated with alloc_stack.
 *
 * @param stack the stack to free
 */
static void
free_stack(char *stack)
{
        page_free_n(stack, 1 + (DEFAULT_STACK_SIZE >> PAGE_SHIFT));
}

/*
 * Allocate a new stack with the alloc_stack function. The size of the
 * stack is DEFAULT_STACK_SIZE.
 *
 * Don't forget to initialize the thread context with the
 * context_setup function. The context should have the same pagetable
 * pointer as the process.
 */
kthread_t *
kthread_create(struct proc *p, kthread_func_t func, long arg1, void *arg2)
{
	/*6.21:kt_errno,qlink,wchan，retval不知道是做什么的*/
	KASSERT(NULL != p); /* should have associated process */
	dbg(DBG_THR,"(GRADING 3.a)In kthread_create(): The thread have associated process %s(pid:%d)\n",p->p_comm,p->p_pid);

	kthread_t *newthread = NULL;
	newthread = (kthread_t *)slab_obj_alloc(kthread_allocator);
	KASSERT(NULL != newthread);
	memset(newthread, 0, sizeof(kthread_t));
	newthread->kt_kstack = alloc_stack();
	KASSERT(NULL != newthread->kt_kstack);

	context_setup(&(newthread->kt_ctx), func, arg1, arg2,
            newthread->kt_kstack, DEFAULT_STACK_SIZE, p->p_pagedir);
	newthread->kt_proc = p;
	/*newthread->kt_errno;*/
	list_init(&newthread->kt_plink);
	list_insert_tail(&p->p_threads, &newthread->kt_plink);
	/*kt_qlink：当这个thread要进入一个queue的时候把qlink连到那个queue上，即连到wchan上*/
	list_init(&newthread->kt_qlink);
	/*wchan指向现在thread所在的queue*/
	newthread->kt_wchan = NULL;
	newthread->kt_state = KT_NO_STATE;
	newthread->kt_retval = 0;
	newthread->kt_errno=0;

	dbg(DBG_THR,"Created Thread(%s) of process(PID=%d)\n",p->p_comm,p->p_pid);

	return newthread;
/*        NOT_YET_IMPLEMENTED("PROCS: kthread_create");*/

}

void
kthread_destroy(kthread_t *t)
{
        KASSERT(t && t->kt_kstack);
        free_stack(t->kt_kstack);
        if (list_link_is_linked(&t->kt_plink))
                list_remove(&t->kt_plink);
        int pid = t->kt_proc->p_pid;
        slab_obj_free(kthread_allocator, t);
        dbg(DBG_MM,"thread(PID=%d) was destroyed cleanly\n",pid);
}

/*
 * If the thread to be cancelled is the current thread, this is
 * equivalent to calling kthread_exit. Otherwise, the thread is
 * sleeping and we need to set the cancelled and retval fields of the
 * thread.
 *
 * If the thread's sleep is cancellable, cancelling the thread should
 * wake it up from sleep.
 *
 * If the thread's sleep is not cancellable, we do nothing else here.
 */
void
kthread_cancel(kthread_t *kthr, void *retval)
{
	/*6.22-2：修改版*/
	KASSERT(NULL != kthr); /* should have thread */
	dbg(DBG_THR,"(GRADING 3.b)In kthread_cancel(): thread of proc %s(pid:%d)to cancel\n",kthr->kt_proc->p_comm,kthr->kt_proc->p_pid);

	if(kthr==curthr)
		kthread_exit(retval);
	else{
		kthr->kt_cancelled = 1;
		kthr->kt_retval = retval;
		if(kthr->kt_state == KT_SLEEP_CANCELLABLE){
			sched_cancel(kthr);
			sched_make_runnable(kthr);/*make runnable,还是应该在sched_cancel()里进行？*/
		}
	}
/*        NOT_YET_IMPLEMENTED("PROCS: kthread_cancel");*/
}

/*
 * You need to set the thread's retval field, set its state to
 * KT_EXITED, and alert the current process that a thread is exiting
 * via proc_thread_exited.
 *
 * It may seem unneccessary to push the work of cleaning up the thread
 * over to the process. However, if you implement MTP, a thread
 * exiting does not necessarily mean that the process needs to be
 * cleaned up.
 */
void
kthread_exit(void *retval)
{
	/*6.22-2:修改版，感觉destroy是在process中进行的，*/
	KASSERT(!curthr->kt_wchan); /* queue should be empty */
	dbg(DBG_THR,"(GRADING 3.c)In kthread_exit(): The thread of proc %s(pid:%d) is not blocking on any list\n",curthr->kt_proc->p_comm,curthr->kt_proc->p_pid);
	KASSERT(!curthr->kt_qlink.l_next && !curthr->kt_qlink.l_prev); /* queue should be empty */
	dbg(DBG_THR,"(GRADING 3.c)In kthread_exit(): The thread of proc %s(pid:%d) is not on any list\n",curthr->kt_proc->p_comm,curthr->kt_proc->p_pid);
	KASSERT(curthr->kt_proc == curproc);
	dbg(DBG_THR,"(GRADING 3.c)In kthread_exit(): The process is current process %s(pid:%d)\n",curproc->p_comm,curproc->p_pid);

	curthr->kt_retval=retval;
	curthr->kt_state=KT_EXITED;
	dbg(DBG_THR,"In kthread_exit(): thread with pid %d exit\n",curthr->kt_proc->p_pid);
	proc_thread_exited(retval);
/*        NOT_YET_IMPLEMENTED("PROCS: kthread_exit");*/
}

/*
 * The new thread will need its own context and stack. Think carefully
 * about which fields should be copied and which fields should be
 * freshly initialized.
 *
 * You do not need to worry about this until VM.
 */
kthread_t *
kthread_clone(kthread_t *thr)
{
	kthread_t *clonethread;
	clonethread=(kthread_t*) slab_obj_alloc(kthread_allocator);
	memset(clonethread, 0, sizeof(kthread_t));
	clonethread->kt_kstack = alloc_stack();
	context_setup(&(clonethread->kt_ctx),NULL, NULL, NULL,clonethread->kt_kstack, DEFAULT_STACK_SIZE, thr->kt_proc->p_pagedir);
	clonethread->kt_proc=thr->kt_proc;
	list_init(&clonethread->kt_plink);
	if(thr->kt_wchan!=NULL)
	{
		 list_insert_head(&thr->kt_wchan->tq_list, &clonethread->kt_qlink);
        	 clonethread->kt_wchan = thr->kt_wchan;
        	 clonethread->kt_wchan->tq_size++;
	}
	clonethread->kt_state=thr->kt_state;
	clonethread->kt_retval=thr->kt_retval;
	clonethread->kt_errno=thr->kt_errno;
	clonethread->kt_cancelled=thr->kt_cancelled;
	
	KASSERT(KT_RUN == thr->kt_state);
    dbg(DBG_VM,"(GRADING 7.a)  thr is running in KT_RUN\n");
	return clonethread;
        /*NOT_YET_IMPLEMENTED("VM: kthread_clone");
        return NULL;*/
}

/*
 * The following functions will be useful if you choose to implement
 * multiple kernel threads per process. This is strongly discouraged
 * unless your weenix is perfect.
 */
#ifdef __MTP__
int
kthread_detach(kthread_t *kthr)
{
        NOT_YET_IMPLEMENTED("MTP: kthread_detach");
        return 0;
}

int
kthread_join(kthread_t *kthr, void **retval)
{
        NOT_YET_IMPLEMENTED("MTP: kthread_join");
        return 0;
}

/* ------------------------------------------------------------------ */
/* -------------------------- REAPER DAEMON ------------------------- */
/* ------------------------------------------------------------------ */
static __attribute__((unused)) void
kthread_reapd_init()
{
        NOT_YET_IMPLEMENTED("MTP: kthread_reapd_init");
}
init_func(kthread_reapd_init);
init_depends(sched_init);

void
kthread_reapd_shutdown()
{
        NOT_YET_IMPLEMENTED("MTP: kthread_reapd_shutdown");
}

static void *
kthread_reapd_run(int arg1, void *arg2)
{
        NOT_YET_IMPLEMENTED("MTP: kthread_reapd_run");
        return (void *) 0;
}
#endif
